package com.cloud.point.concurrent.general;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicMarkableReference;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

/**
 * Atomic 类的使用
 *
 * @author 24373
 * @version 1.0
 * @date 2024-06-17 18:42
 */
@Slf4j
public class TestAtomic {

    private volatile String name;
    private static final AtomicReference<String> ref = new AtomicReference<>("A");
    private static final AtomicStampedReference<String> refStamp = new AtomicStampedReference<>("A", 0);
    private static final AtomicMarkableReference<String> refMarkable = new AtomicMarkableReference<>("A", true);

    public static void main(String[] args) throws InterruptedException {
        AtomicInteger ato = new AtomicInteger(0);
        // i++
        log.info("{}", ato.getAndIncrement());
        // ++i
        log.info("{}", ato.incrementAndGet());
        // i+=2
        log.info("{}", ato.getAndAdd(2));
        // i=i*10
        log.info("{}", ato.updateAndGet(t -> t * 10));

        /**
         * 原子引用类型的使用
         */
        String prev = ref.get();
        String next = "C";
        TimeUnit.SECONDS.sleep(1);
        testABA();
        TimeUnit.SECONDS.sleep(1);
        ref.compareAndSet(prev, next);
        log.info(ref.get());

        /**
         * ABA问题的解决；使用原子引用带版本
         */
        String next2 = "C";
        int oldStamp = refStamp.getStamp();
        int nextStamp = oldStamp + 1;
        testABAStamp();
        TimeUnit.SECONDS.sleep(1);
        refStamp.compareAndSet(refStamp.getReference(), next2, oldStamp, nextStamp);
        log.info("refStamp3" + refStamp.getReference());

        /**
         * 原子引用只关心有无改动
         */
        log.info("refMarkable1" + refMarkable.getReference());
        testABAMarkable();
        TimeUnit.SECONDS.sleep(1);
        refMarkable.compareAndSet("A", "C", true, false);
        log.info("refMarkable2" + refMarkable.getReference());

        /**
         * 原子数组的使用
         */
        demo(
                () -> new int[10],
                (t) -> t.length,
                (t, index) -> t[index]++,
                t -> log.info(Arrays.toString(t))
        );
        demo(
                () -> new AtomicIntegerArray(10),
                (t) -> t.length(),
                (t, index) -> t.getAndIncrement(index),
                t -> log.info("{}", t)
        );

        /**
         * 字段更新器
         */
        AtomicReferenceFieldUpdater<TestAtomic, String> updater =
                AtomicReferenceFieldUpdater.newUpdater(TestAtomic.class, String.class, "name");
        TestAtomic testAtomic = new TestAtomic();
        updater.compareAndSet(testAtomic, null, "test2");
        log.info(testAtomic.name);

        /**
         * 原子累加器
         */
        for (int i = 0; i < 5; i++) {
            add(() -> new LongAdder(), t -> t.increment());
        }
        log.info("=================");
        for (int i = 0; i < 5; i++) {
            add(() -> new AtomicLong(), t -> t.getAndIncrement());
        }

    }

    private static <T> void add(Supplier<T> s, Consumer<T> t) {
        T addr = s.get();
        List<Thread> list = new ArrayList<>();
        StopWatch watch = new StopWatch();
        watch.start();
        for (int i = 0; i < 4; i++) {
            list.add(
                    new Thread(() -> {
                        for (int j = 0; j < 500000; j++) {
                            t.accept(addr);
                        }
                    })
            );
        }
        list.forEach(Thread::start);
        list.forEach(l -> {
            try {
                l.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        watch.stop();
        log.info("result|<|>|{}|<|>|{}", addr, watch.getTotalTimeNanos());

    }

    private static <T> void demo(
            Supplier<T> arraySupplier,
            Function<T, Integer> lengthFun,
            BiConsumer<T, Integer> putConsumer,
            Consumer<T> printConsumer
    ) {
        List<Thread> threadList = new ArrayList<>();
        T tArray = arraySupplier.get();
        Integer length = lengthFun.apply(tArray);
        for (int i = 0; i < length; i++) {
            threadList.add(
                    new Thread(() -> {
                        for (int j = 0; j < 10000; j++) {
                            // 每个数自增1w次
                            putConsumer.accept(tArray, j % length);
                        }
                    })
            );
        }
        threadList.forEach(t -> t.start());
        threadList.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        printConsumer.accept(tArray);

    }

    private static void testABAMarkable() throws InterruptedException {
        String prev = refMarkable.getReference();
        String next = "B";
        String oldPrev = "A";
        new Thread(() -> {
            log.info("{}", refMarkable.compareAndSet(prev, next, true, false));
        }, "t3").start();
        TimeUnit.SECONDS.sleep(1);
        log.info("refStamp1" + refMarkable.getReference());
        new Thread(() -> {
            log.info("{}", refMarkable.compareAndSet(next, oldPrev, true, false));
        }, "t4").start();
        TimeUnit.SECONDS.sleep(1);
        log.info("refStamp2" + refMarkable.getReference());
    }

    private static void testABAStamp() throws InterruptedException {
        String prev = refStamp.getReference();
        String next = "B";
        String oldPrev = "A";
        new Thread(() -> {
            log.info("{}", refStamp.compareAndSet(prev, next, refStamp.getStamp(), refStamp.getStamp() + 1));
        }, "t3").start();
        TimeUnit.SECONDS.sleep(1);
        log.info("refStamp1" + refStamp.getReference());
        new Thread(() -> {
            log.info("{}", refStamp.compareAndSet(next, oldPrev, refStamp.getStamp(), refStamp.getStamp() + 1));
        }, "t4").start();
        TimeUnit.SECONDS.sleep(1);
        log.info("refStamp2" + refStamp.getReference());
    }

    /**
     * ABA问题演示
     * C-b-c-A
     */
    private static void testABA() throws InterruptedException {
        String prev = ref.get();
        String next = "B";
        String oldPrev = "A";
        new Thread(() -> {
            log.info("{}", ref.compareAndSet(prev, next));
        }, "t1").start();
        TimeUnit.SECONDS.sleep(1);
        log.info("1" + ref.get());
        new Thread(() -> {
            log.info("{}", ref.compareAndSet(next, oldPrev));
        }, "t2").start();
        TimeUnit.SECONDS.sleep(1);
        log.info("2" + ref.get());

    }

}
